package com.hpe.aruba.CPPM.utils;

import android.util.Log;

import com.android.volley.RequestQueue;
import com.hpe.aruba.CPPM.Data;
import com.hpe.aruba.CPPM.LoginActivity;
import com.hpe.aruba.CPPM.ManageMacsActivity;
import com.hpe.aruba.CPPM.interfaces.CreateDeviceInterface;
import com.hpe.aruba.CPPM.interfaces.JsonInterface;
import com.hpe.aruba.CPPM.interfaces.MacListActivity;
import com.hpe.aruba.CPPM.objects.Device;
import com.hpe.aruba.CPPM.objects.Role;
import com.hpe.aruba.CPPM.objects.Token;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.hpe.aruba.CPPM.utils.MacAdressUtils.baseMac;
import static com.hpe.aruba.CPPM.utils.MacAdressUtils.normalizeMac;

public class ClearpassUtils {

    private static final String TAG = "CLEARPASS";
    private RequestQueue queue;


    public ClearpassUtils(RequestQueue queue) {
        this.queue = queue;
    }

    public void deleteDevice(Device device) {
        String url = Data.baseUrl + "/api/device/" + device.getId();
        WebUtils.delete(queue, url, getHeader(), resp -> {
        }, error -> {
        });
    }

    private void getDevicesFromPage(ManageMacsActivity activity,
                                      ArrayList<Device> devices, String page) {
        WebUtils.getJson(queue, page, getHeader(), resp -> {
            Log.e(TAG, resp.toString());
            try {
                JSONArray items = resp.getJSONObject("_embedded").getJSONArray("items");
                for (int i = 0; i < items.length(); i++) {
                    JSONObject device = items.getJSONObject(i);
                    String macAddress = device.getString("username");
                    macAddress = normalizeMac(macAddress);
                    Device device1 = parseDeviceFromJson(new Device(macAddress), device);
                    devices.add(device1);
                }

                JSONObject links = resp.getJSONObject("_links");
                try { /*is there a next page?*/
                    String nextPage = links.getJSONObject("next").getString("href");
                    /*if yes get the next page */
                    getDevicesFromPage(activity, devices, Data.baseUrl + nextPage);
                } catch (JSONException noNext) {
                    /*no next page, all were processed: return device list*/
                    activity.processDevices(devices);
                }
            } catch (JSONException e) {
                Log.e("getDevice", "error Parsing response");
            }
        }, error -> {
            Log.e(TAG, error);
        });
    }

    public void getDevices(ManageMacsActivity activity) {
        /*gets devices and calls ManageMacsActivity.processDevices(devices)*/
        /*url with limit at 1000, count and no filter*/
        String url = Data.baseUrl + "/api/device?filter=%7B%7D&sort=%2Bid&offset=0&limit=1000&calculate_count=false";
        ArrayList<Device> devices = new ArrayList<>();
        this.getDevicesFromPage(activity, devices, url);
    }

    private void getEndpointsFromPage(long timeStamp, ManageMacsActivity activity,
                                  ArrayList<Device> devices, String page) {
        WebUtils.getJson(queue, page, getHeader(), resp -> {
            Log.e(TAG, resp.toString());
            try {
                JSONArray items = resp.getJSONObject("_embedded").getJSONArray("items");
                Device device1 = new Device();
                for (int i = 0; i < items.length(); i++) {
                    JSONObject device = items.getJSONObject(i);
                    String macAddress = device.getString("mac_address");
                    macAddress = normalizeMac(macAddress);
                    device1 = parseEndpointFromJson(new Device(macAddress), device);
                    /*last flag is true if it is the last item of the page*/
                    boolean lastItemOfPage = (i == items.length()-1);
                    if (!lastItemOfPage) {
                        /*if it isnt the last item of the page, add it*/
                        this.insightOrNot(device1, timeStamp, devices,null);
                    }
                }


                JSONObject links = resp.getJSONObject("_links");
                try { /*is there a next page? if yes add the last device of the page and load the next*/
                    String nextPage = links.getJSONObject("next").getString("href");
                    this.insightOrNot(device1,timeStamp,devices, null);
                    /*if yes get the next page */
                    getEndpointsFromPage(timeStamp, activity, devices, Data.baseUrl + nextPage);
                } catch (JSONException noNext) {
                    /*if not add the last item and process them as soon as it was added*/
                    this.insightOrNot(device1, timeStamp, devices, activity);
                }
            } catch (JSONException e) {
                Log.e("getDevice", "error Parsing response");
            }
        }, error -> {
            Log.e(TAG, error);
        });
    }

    private void insightOrNot(Device device, long timeStamp,
                              ArrayList<Device> devices, ManageMacsActivity activity) {
        if (timeStamp != 0) {
            this.addEndpointWithInsightIfTimeMatch(device, timeStamp, devices, activity);
        } else {
            devices.add(device);
            if (activity != null) activity.processDevices(devices);
        }
    }

    /*get endpoints since timeStamp - endpoints without insight entry will be ignored
    * enter timeStamp = 0 to not check for insight
    * TODO: clean up
    * */
    public void getEndpointsSince(long timeStamp, ManageMacsActivity activity) {
        /*gets devices and calls ManageMacsActivity.processDevices(devices)*/
        /*url with limit at 1000, count and no filter*/
        String url = Data.baseUrl + "/api/endpoint?filter=%7B%7D&sort=%2Bid&offset=0&limit=1000&calculate_count=false";
        ArrayList<Device> devices = new ArrayList<>();
        this.getEndpointsFromPage(timeStamp, activity, devices, url);
    }

    private void addEndpointWithInsightIfTimeMatch(Device device, long timeStamp,
                                                   ArrayList<Device> devices, ManageMacsActivity activity) {
        /*TODO: curently one request for EVERY device, try to reduce to one*/
        String url = Data.baseUrl + "/api/insight/endpoint/mac/" + baseMac(device.getMac());
        if (activity == null) {
            WebUtils.getJson(queue, url, getHeader(), resp -> {
                Log.e(TAG, resp.toString());
                try {
                    String dateString = resp.getString("updated_at");
                    /*get datetime, full ISO 8601 parsing only available in >26
                     * see: https://stackoverflow.com/questions/3941357 */
                    dateString = dateString.substring(0, 19); /*cut away time zone and time; only date*/
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                    device.setUpdated_at(format.parse(dateString).getTime());

                    if (device.getUpdated_at() / 1000L >= timeStamp) { /* if timestamp is bigger than timeStamp */
                    /*try {
                        //parse attributes here
                    } catch (JSONException e) {}*/
                            devices.add(device);
                    }

                    /*if activity was passed, it means this is the last of the object, so pass the list back*/
                } catch (JSONException e) {
                    Log.e("getDevice", "error Parsing response");
                } catch (ParseException e) {
                    Log.e(TAG, "Error parsing updated_at tag. Not including device.");
                }
            }, error -> {
                Log.e(TAG, "" + error);
            });
        } else {
            WebUtils.getJson(queue, url, getHeader(), resp -> {
                Log.e(TAG, resp.toString());
                try {
                    String dateString = resp.getString("updated_at");
                    /*get datetime, full ISO 8601 parsing only available in >26
                     * see: https://stackoverflow.com/questions/3941357 */
                    dateString = dateString.substring(0, 19); /*cut away time zone and time; only date*/
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                    device.setUpdated_at(format.parse(dateString).getTime());
                    if (device.getUpdated_at() / 1000L >= timeStamp) { /* if timestamp is bigger than timeStamp */
                    /*try {
                        //parse attributes here
                    } catch (JSONException e) {}*/
                        devices.add(device);
                    }

                    /*if activity was passed, it means this is the last of the object, so pass the list back*/
                } catch (JSONException e) {
                    Log.e("getDevice", "error Parsing response");
                } catch (ParseException e) {
                    Log.e(TAG, "Error parsing updated_at tag. Not including device.");
                }
                activity.processDevices(devices);
            }, error -> {
                Log.e(TAG, "" + error);
                activity.processDevices(devices);
            });
        }
    }

    private Device parseDeviceFromJson(Device mac, JSONObject device) throws JSONException {
        /*optString if it is optional, getString if it is necessary -> device gets thrown out if it's not set*/
        mac.setName(device.optString("username"));
        mac.setMac(device.optString("mac"));
        mac.setVendor(device.optString("vendor"));
        mac.setNotes(device.optString("notes"));
        mac.setCreate_time(device.optString("create_time"));
        mac.setId(device.getInt("id"));
        mac.setName(device.optString("username"));
        mac.setRole_name(device.optString("role_name"));
        mac.setRole_id(device.getInt("role_id"));
        mac.setImported(true);
        return mac;
    }

    private Device parseEndpointFromJson(Device device, JSONObject deviceJson) throws JSONException {
        /*optString if it is optional, getString if it is necessary -> device gets thrown out if it's not set*/
        device.setName(deviceJson.optString("username"));
        String mac = deviceJson.getString("mac_address");
        device.setMac(normalizeMac(mac));
        device.setVendor(deviceJson.optString("vendor"));
        device.setLast_known_location(deviceJson.getJSONObject("attributes")
                .optString("Last Known Location"));
        device.setNotes(deviceJson.optString("description"));
        device.setCreate_time(deviceJson.optString("create_time"));
        device.setId(deviceJson.optInt("id"));
        device.setName(deviceJson.optString("username"));
        device.setRole_name(deviceJson.optString("role_name"));
        device.setRole_id(deviceJson.optInt("role_id"));
        device.setStatus(deviceJson.getString("status"));
        device.setEndpoint(true);
        device.setImported(true);
        return device;
    }

    public void getToken(String uname, String passwd, String clientId, LoginActivity loginActivity) {
        String url = Data.baseUrl + "/api/oauth";

        JSONObject json = new JSONObject();
        try {
            json.put("grant_type", "password");
            json.put("client_id", clientId);
            json.put("client_secret", "");
            json.put("username", uname);
            json.put("password", passwd);
            json.put("scope", "");
            json.put("refresh_token", "");
        } catch (JSONException e) {
            e.printStackTrace();
        }

        WebUtils.postJson(queue, url, new HashMap<>(), json, resp -> {
            Log.e(TAG, resp.toString());
            try {
                Token token = new Token(resp.getString("token_type"), resp.getString("access_token"), resp.getInt("expires_in"));
                loginActivity.processToken(token);
            } catch (JSONException e) {
                Log.e("Authentication", "error Parsing response");
            }
        }, error -> {
            String message;
            try {
                JSONObject jsonObject = new JSONObject(new String(error.networkResponse.data));
                Log.e(TAG, jsonObject.getString("detail"));
                message = jsonObject.getString("detail");
            } catch (Exception e) {
                message = "";
            }
            loginActivity.onLoginFailed(message);
        });
    }

    /**
     * uploads devices which have the imported flag set to false
     */
    public void createDevices(ArrayList<Device> macs, ManageMacsActivity manageMacsActivity) {
        String url = Data.baseUrl + "/api/device";
        Map<String, String> header = getHeader();

        macs.stream().filter(device -> !device.isImported()).forEach(mac -> {
            JSONObject json = mac.toJson();
            Log.d(TAG, header.toString());
            WebUtils.postJson(queue, url, header, json, resp -> {
                try {
                    parseDeviceFromJson(mac, resp);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                manageMacsActivity.updateMac(); /*this is supposed to launch an update for the list of mma.macs - seems pointless since it doesnt get changed here?*/
            }, error -> Log.e(TAG, "" + error.getMessage()));
        });
    }

    public void createDevice(Device device, CreateDeviceInterface createDeviceInterface) {
        String url = Data.baseUrl + "/api/device";
        Map<String, String> header = getHeader();

        JSONObject json = device.toJson(); /*try catch this*/
        WebUtils.postJson(queue, url, header, json, resp -> {
            try {
                parseDeviceFromJson(device, resp);
                createDeviceInterface.onSuccess();
            } catch (JSONException e) {
                createDeviceInterface.onError("parsing");
            }
        }, error -> {
            int errCode = error.networkResponse.statusCode;
            if (errCode == 422) { /*422 unprocessable entity*/
                createDeviceInterface.onError("422");
            } else {
                createDeviceInterface.onError("");
            }
        });

    }

    private Map<String, String> getHeader() {
        Map<String, String> header = new HashMap<>();
        header.put("Authorization", Data.token);
        header.put("Accept", "application/json");
        return header;
    }

    /*fills roles in Data.roles*/
    public void getRoles() {
        String url = Data.baseUrl + "/api/role?filter=%7B%7D&sort=%2Bid&offset=0&limit=1000&calculate_count=true";

        WebUtils.getJson(queue, url, getHeader(), resp -> {
            Log.e(TAG, resp.toString());
            try {
                JSONArray items = resp.getJSONObject("_embedded").getJSONArray("items");
                List<Role> roles = new ArrayList<>();
                for (int i = 0; i < items.length(); i++) {
                    JSONObject role = items.getJSONObject(i);
                    roles.add(new Role(role.getInt("id"), role.getString("name")));
                }
                Log.e("Roles", roles.toString());
                Data.roles = roles;
            } catch (JSONException e) {
                Log.e("Authentication", "error Parsing response");
            }
        }, error -> {
            Log.e(TAG, ""+error);
        });
    }

    /*gets guest roles with mapped id*/
    public void getGuestRoles(JsonInterface jsonInterface) {
        String url = Data.baseUrl + "/api/role-mapping/name/%5BGuest%20Roles%5D";

        WebUtils.getJson(queue, url, getHeader(), resp -> {
            Log.e(TAG, resp.toString());
            try {
                String name;
                int id;
                JSONArray items = resp.getJSONArray("rules");
                List<Role> roles = new ArrayList<>();
                for (int i = 0; i < items.length(); i++) {
                    JSONObject rule = items.getJSONObject(i);
                    name = rule.getString("role_name");
                    /*as far as we know not more than one condition*/
                    JSONObject condition = rule.getJSONArray("condition").getJSONObject(0);
                    id = Integer.parseInt(condition.getString("value"));
                    roles.add(new Role(id, name));
                }
                Log.e("Guest_Roles", roles.toString());
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("roles", roles);
                jsonInterface.apply(jsonObject);
            } catch (JSONException e) {
                Log.e("GUESTROLES", "Parsing error? " + e.getMessage());
            }
        }, error -> {
            Log.e(TAG, error);
        });
    }

    /**
     * patches an endpoint or device.
     * first argument: String "endpoint" for endpoints, anything else for devices.
     * second: mac in string form. Will be baseMac'd
     * third: JSONObject which will be sent as patch.
     * fourth: MacListActivity on which syncDevices will be called
     * */
    public void patchEndpointOrDevice(String kind, String mac, JSONObject patch, MacListActivity activity, JsonInterface errorInterface) {
        String url;
        if (kind.equals("endpoint")) {
            url = Data.baseUrl + "/api/endpoint/mac-address/" + MacAdressUtils.baseMac(mac);
        } else {
            url = Data.baseUrl + "/api/device/mac/" + MacAdressUtils.baseMac(mac);
        }

        /*patch*/
        WebUtils.patch(queue, url, patch,getHeader(), resp -> {
            activity.syncDevices();
            errorInterface.apply(new JSONObject()); /*non null for success*/
        }, error -> {
            Log.e(TAG, ""+error);
            errorInterface.apply(null); /*null for error*/
        });
    }

}
